
#ifndef NRPSTG_TERRAIN_H
#define	NRPSTG_TERRAIN_H
#include <stddef.h>
#include "Common.h"

class Cell;
class Block;
class BlockRenderer;

class TerrainDefinition;

const UNIT CELL_WHD[3] = {CELL_WIDTH, CELL_HEIGHT, CELL_DEPTH};

/*
 * This structure is used when "normalising" the position of a block "inside" a cell
 */
struct BlockPos {
    Cell* cell;
    UNIT pos[3];
};

/**
 * A Block is the base voxel within our system
 */
class Block {
public:
    Block(Cell* parent);
    virtual ~Block();

    UNIT  pos[3];
    Cell* parent; // The cell that contains this block
    UNIT  depth; // The depth of the block from the terrain root.
    int   terrain_def_id;// The id of the terrain definition, used to get a reference from the SceneModel
    bool  do_draw;
    TerrainDefinition* terrain_def;

    Block* next;
    Block* prev;

    int seed; // A number used for seeding any random functions used on this block when rendering
    int block_type; //

    virtual byte get_type();

    void set_pos(UNIT* pos);
    
    bool is_at(UNIT* pos); //Returns true if this block is at this position

    byte* to_byte_array(); // Create a byte array that represents this block.
    Block* from_array(byte* block_def); // Reverse of to_byte_array()

    bool check_draw();
    void draw();

    /**
     * Get the position of the cell that neighbours this cell in the given direction.
     * Note that the position returned may be out side of the scope of the parent
     * of this cell.
     *
     * @param dir the direction the neighbouring cell is in from this cell, see WORLD_XP etc.
     * @return the position of the neighbouring cell as a UNIT array.
     */
    void get_neighbour_pos(UNIT ret[3], UNIT dir);
private:

};

/**
 * A Cell is a Block that can have children
 *
 * A cell also holds references to cells on each face, like a linked list
 * To visualise the reference, the world coordinates are aligned so that
 * X+ is to the right, Y+ is to the top, and Z+ is out of the screen (RHR)
 * This is denoted as xp, xn, yp, yn, zp, zn
 */
class Cell : public Block {
public:

    Cell(UNIT depth, Cell* parent);
    virtual ~Cell();

    /*
     * The cells surrounding this cell, this forms a linked graph use WORLD_XP etc
     * to get the index of for the block in a given direction.
     */
    Cell* neighbours[8];
    Block* first;
    Block* last; // The first and last child blocks in this cell
    
    int count; // The number of blocks within the cell
    UNIT state; //See states above

    /* The actual blocks within a cell in a 2D array, this array isn't created
     * Until _one_ block(s) have been added within the cell and basicly acts as a
     * hash map to the blocks within the cell.
     * 
     * TODO: Work out how many blocks should be in a cell should be added before
     * the memory / load trade off is best managed
     */
    Block** blocks;

    /**
     * This is a util method that will return an index to the block array based
     * on the given position array.
     *
     * @param pos A position array.
     * @return the index that the block for this position would be in the block array.
     */
    int pos_to_index(UNIT* pos);

    /**
     * Get the block at the given position, if the position lays outside the limits
     * of this cell a block will be return from the cell where the position is vaild.
     * see normalise_block_pos() for how this works.
     *
     * @param pos the position of the cell you want to retrieve.
     * @return The block at the given position.
     */
    Block* get_block(UNIT* pos);

    /**
     * This is a wrapper to the get_block method that will cast the block into a
     * cell if it is infact a cell. This method will return null if there is no
     * block at the given position or the block is not a cell so beware. Don't
     * call this method unless you know that the block you want is actually a cell
     * k guy.
     *
     * @param pos The position of the cell you want to get.
     * @return the cell at the given position, or null.
     */
    Cell* get_cell(UNIT* pos);

    /**
     * Set a block in the cell at the given coordinates, if the cell index is out of the
     * scope of this cell this method will attempt to get the heigbouring cell
     * and get the block from it using the _normalise_position() method.
     *
     * WARNING! this method will replace any existing block and DESTROY IT.
     *
     * @param pos the position to insert the block.
     * @param block the block to insert.
     * @return true if the block was set and false if setting the block failed
     */
    Cell* set_block(UNIT* pos, Block* block);

    /**
     * Somewhat like add this method will set a block within a cell, but in this
     * method if there is already an existing block in the cell it will try to merge
     * it's contents with the given block. the depth of this merge can be limited using
     * depth param, 0 = will replace only first level child blocks or an existing block.
     * if you want an unlimited merge set depth to -1.
     *
     * @param pos The postion you want the new block in.
     * @param block The block to insert
     * @param depth how many levels to merge
     * @return
     */
    //Cell* merge_block(UNIT* pos, Block* block, UNIT depth); //TODO:
    void merge_block(UNIT* pos, Block* block, UNIT depth);

    /**
     * The position passed to this method may lay outside of this cell, if this
     * is the case then this method will return the cell and position within that
     * cell that the position passed to this method actually is in, such that
     * x < CELL_WIDTH, y < CELL_HEIGHT, z < CELL_DEPTH.
     *
     * if the position is valid within this cell then this method will simply
     * return a BlockPos with the cell as this and the pos ad the position passed in.
     *
     * @param pos the position to transform
     * @return a new BlockPos with the real cell and position of the pos passed in. NOTE YOU NEED TO DELETE THIS OBJECT TO STOP MEMORY LEAKS!
     */
    BlockPos* normalise_block_pos(UNIT* pos);

    /**
     * This method gets the neighbour of this cell in the given direction.
     *
     * @param dir the direction of the cell to fetch, see WORLD_XP etc.
     * @return The cell in the given direction (or null if there is none).
     */
    Cell* get_neighbour(UNIT dir);



    /**
     * Returns BLOCK_TYPE_CELL as this is the type of block a cell is =]
     * @return BLOCK_TYPE_CELL
     */
    byte get_type();
private:

    /**
     * Get's the actual location of a block assuming that x,y and z might infact
     * be outside of the scope of this cell. This method will return a tuple containing
     * the actual cell the block should be in and the x,z and y location the block
     * be within that cell
     *
     * @param pos
     * @return A BlockPos with the actual cell the block is in and the postion within that cell.
     */
    bool _normalise_position(BlockPos* bp, UNIT* pos);

    /**
     * Registers a block to the cell, 
     * @param pos
     * @param block
     */
    void _register_child(UNIT* pos, Block* block);

    /**
     * Registers a cell with it's surrounding cells and it's surrounding cells with
     * it self. This includes cells that are out side of this cell (the parent cell)
     *
     * @param pos the position the cell is in
     * @param cell the cell to be registered
     */
    void _register_cell(UNIT* pos, Cell* cell); //Done

    /**
     * Adds set the neighour of this cell on dir side to cell, and the neighbour of
     * cell on rdir to this.
     * @param the position of the reference cell.
     * @param cell the neighbouring cell.
     * @param dir the direction that that cell is from the reference cell.
     * @param rdir (reverse direction) the direction that the reference cell is from the cell.
     */
    void _register_cell_neighbour(UNIT* pos, Cell* cell, UNIT dir, UNIT rdir); //Done

    /**
     * Get a block from within this cell, unlike the public version of this method
     * this does not try to "jump" cells if the position is not within the cell.
     * if the position is not within the cell this method will return null.
     *
     * @param pos where the block you want is [X,Y,Z]
     * @return the block at the postion given, or null if there is no block at
     * the position or the position is not valid.
     */
    Block* _get_block(UNIT* pos); //Done

    /**
     * This method is call internally when the cell has more blocks then CELL_CRITICAL_MASS
     * this method allocates any array of pointers of the size CELL_WIDTH*CELL_HEIGHT*CELL_DEPTH
     * to be used as a hash map for blocks within the cell.
     */
    void _init_hash(); //Done
};

class TerrainDefinition {
public:
    TerrainDefinition(int id);
    virtual ~TerrainDefinition();

    BlockRenderer* renderer;
    int* textures;
    int id;

private:
};
#endif	/* NRPSTG_TERRAIN_H */

